package fr.bludwarf.skywarrior.joueur;

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Queue;
import java.util.Vector;

import fr.bludwarf.skywarrior.evenements.Colonisation;
import fr.bludwarf.skywarrior.evenements.Evenement;
import fr.bludwarf.skywarrior.exceptions.JoueurInconnu;
import fr.bludwarf.skywarrior.exceptions.RessourcesInsuffisantes;
import fr.bludwarf.skywarrior.global.EtatPere;
import fr.bludwarf.skywarrior.global.Temps;
import fr.bludwarf.skywarrior.global.TimeFactorInconnu;
import fr.bludwarf.skywarrior.ile.Construction;
import fr.bludwarf.skywarrior.ile.EtatIle;
import fr.bludwarf.skywarrior.ile.Ile;
import fr.bludwarf.skywarrior.ile.TypeBatiment;

/**
 * Etat d'un joueur
 * @since 2 nov. 2011
 */
public class EtatJoueur extends EtatPere<Joueur, Ile, EtatIle> {
	
	/** Stocks */
	// TODO : prendre en compte la population
	Ressources _stocks = new Ressources(100, 30, 0);

	/** Nombre de vaisseaux de chaque type */
	List<Vaisseau> _vaisseaux = new Vector<Vaisseau>();

	/** Colonisations prévues */
	List<Ile> _colosPrevues = new Vector<Ile>();

	/** Colonisations en cours */
	List<Colonisation> _colosEnCours = new Vector<Colonisation>();
	
	public EtatJoueur(final Joueur joueur, final EtatPere etatPere) {
		super(joueur, etatPere);
		super.init();
		// De base on a au moins une ile avec un palais
		add(joueur.getIlePrincipale());
	}
	
	public EtatJoueur(final EtatJoueur etat) throws Exception {
		super(etat);
		_stocks = (Ressources) etat._stocks.clone();
	}
	
	/**
	 * @return la production totale de matériaux, d'éther et de nourriture du joueur
	 */
	public Ressources getProduction(final boolean cumulNourriture)
	{
		Ressources prod = new Ressources(0, 0, 0); // FIXME : on commence avec combien de pop
		
		for (final EtatIle etatIle : getEtatsIles().values())
		{
			prod.add(etatIle.getProduction(cumulNourriture), cumulNourriture);
		}
		
		return prod;
	}
	
	/**
	 * Ajoute un nouveau bâtiment dans la liste des bâtiments à construire pour une ile donnée
	 * @param batiment bâtiment à construire
	 * @param ile ile sur laquelle construire le bâtiment
	 * @throws TimeFactorInconnu si le TimeFactor est inconnu pour le type demandé
	 */
	public void construire(final TypeBatiment batiment, final int ile) throws TimeFactorInconnu
	{
		final int num = checkIle(ile);
		getEtatsIles().get(num - 1).construire(batiment);
	}

	/**
	 * Ajoute un nouveau bâtiment dans la liste des bâtiments à construire sur <b>l'ile principale</b> du joueur
	 * @param batiment bâtiment à construire
	 * @throws TimeFactorInconnu si le TimeFactor est inconnu pour le type demandé
	 */
	public void construire(final TypeBatiment batiment) throws TimeFactorInconnu
	{
		construire(batiment, 1);
	}

	/**
	 * Essaye de coloniser l'ile indiquée dès que c'est possible (apicopter, ballon, etc...)
	 * @param nomJoueur nom du joueur qui colonise
	 * @param nomIle nom de l'ile à coloniser
	 * @throws JoueurInconnu 
	 * @since 25 nov. 2011
	 */
	public void coloniser(final Ile ile) {
		_colosPrevues.add(ile);
	}
	
	private int checkIle(int ile) {
		// Ile connue ?
		if (ile >= 1 && ile <= getEtatsIles().size())
		{
			return ile;
		}
		else
		{
			System.err.println("L'ile numéro " + ile + " n'existe pas, on utilise l'ile principale");
			return 1;
		}
	}
	
	/**
	 * @param ile ile dont souhaite obtenir la liste des construction en cours
	 * @return liste des construction en cours de l'ile
	 */
	public Queue<Construction> constructionsEnCours(final int ile)
	{
		final int num = checkIle(ile);
		return getEtatsIles().get(num - 1).getConstructions();
	}
	
	/**
	 * @return la prochaine colonisation à partir de l'état du joueur courant, null si aucune colo prévue
	 */
	public Colonisation colonisationEnCours()
	{
		// On prend la première des colonisations prévues (sinon return null)
		if (_colosPrevues.isEmpty())
		{
			return null;
		}
		
		else
		{
			
			// Il faut au moins un colon sur une ile, sinon temps infini
			if (getNb(TypeVaisseau.APICOPTER) == 0 && getNb(TypeVaisseau.BALLON) == 0)
			{
				return null;
			}
			
			// Ile d'arrivée et de départ
			final Ile ileAColoniser = _colosPrevues.get(0);
			final Ile ileDepart;
			
			// TODO : ajouter un attribut dans Vaisseau pour savoir s'il peut coloniser
			// Colon
			final Vaisseau colon;
			if (getNb(TypeVaisseau.APICOPTER) > 0)
			{
				colon = TypeVaisseau.APICOPTER;
			}
			else
			{
				colon = TypeVaisseau.BALLON;
			}
			
			// Temps pour se déplacer jusqu'à l'ile
			final double tempsTransport = Temps.tempsPourParcourir(ileDepart, ileAColoniser, colon.getVitesse());
			
			// Ajout dans la liste des colos en cours
			final Colonisation colonisation = new Colonisation(getJoueur(), ileAColoniser, colon, tempsTransport);
			_colosEnCours.add(colonisation);
			
			// Suppresion de la liste des colo prévues
			_colosPrevues.remove(0);
			
			return colonisation;
		}
	}

	/**
	 * Ajoute une certaine quantité de matériaux et d'éther. La production de nourriture ne se cumule pas.
	 * @param production quantité de ressource à ajouter au joueur
	 */
	public void ajouter(final Ressources production) {
		_stocks.add(production, false);
	}
	
	@Override
	public String toString() {
		final StringBuilder sb = new StringBuilder();

		final Ressources stocks = getStocks();
		sb.append(_entite.getNom()).append("(m=")
			.append(stocks.materiaux())						// matériaux
			.append(",e=").append(stocks.ether())				// éther
			.append(",u=").append("0")								// unités TODO
			.append("/").append("20").append(")").append(" :");		// unités max TODO
		
		for (final EtatIle etat : getEtatsIles().values())
		{
			sb.append("\n\t").append(etat);
		}

		return sb.toString();
	}

	public HashMap<Ile, EtatIle> getEtatsIles() {
		
		return getEtatsFils();
	}

	public EtatIle getEtatIle(Ile ile) {
		return getEtatsIles().get(ile);
	}

	@Override
	// TODO : prochainEvenement
	public Evenement prochainEvenement() {
		Collection<Evenement> evenements = new Vector<Evenement>();
		
		// prochaine techno
		
		// prochaine conquête d'ile
		if (!_colosEnCours.isEmpty())
		{
			// Temps de la première des colonisations en cours (ou restantes)
			evenements.add(_colosEnCours.get(0));
		}
		
		// prochain évènement parmis toutes les iles
		evenements.add(Evenement.prochainEvenementDe(getEtatsIles().values()));
		
		return Evenement.prochainEvenementParmis(evenements);
	}

	@Override
	protected EtatIle etatInitialFils(Ile fils) {
		return new EtatIle(fils, this);
	}

	public void setStocks(int materiaux, int ether) {
		_stocks.materiaux(materiaux);
		_stocks.ether(ether);
	}
	
	public Ressources getStocks()
	{
		return _stocks;
	}
	
	/**
	 * @return true si on peut continuer ou commencer les constructions sur au moins une ile
	 * @since 10 nov. 2011
	 */
	public boolean peutConstruire()
	{
		final HashMap<Ile, EtatIle> etatsIles = getEtatsIles();
		for (final Ile ile : etatsIles.keySet())
		{
			final EtatIle etatIle = etatsIles.get(ile);
			if (etatIle.peutConstruire())
			{
				return true;
			}
		}
		
		return false;
	}

	/**
	 * @param type type du vaisseau recherché
	 * @return le nombre de vaisseaux (terminés) du type recherché pour ce joueur
	 */
	public int getNb(TypeVaisseau type) {
		int nb = 0;
		for (final Vaisseau vaisseau : _vaisseaux)
		{
			if (vaisseau.estDeType(type))
			{
				++nb;
			}
		}
		return nb;
	}

	public void consommer(Ressources cout) throws RessourcesInsuffisantes {
		_stocks.remove(cout);
	}

	/**
	 * Prise en compte uniquement des matériaux et de l'éther
	 * @param ress
	 * @return
	 * @since 14 nov. 2011
	 */
	public double tempsPourCollecter(Ressources cout) {
		// Production
		final Ressources prod = getProduction(false);
		
		// Temps max pour collecter les matériaux
		double tMat = getTemps().tempsPourCollecter(cout.materiaux(), prod.materiaux(), _stocks.materiaux());
		if (tMat > 0)
		{
			System.out.println("On doit attendre " + Temps.toString(tMat) + " pour collecter " + cout.materiaux() + " matériaux");
		}
		
		// Temps max pour collecter l'éther
		double tEth = getTemps().tempsPourCollecter(cout.ether(), prod.ether(), _stocks.ether());
		if (tEth > 0)
		{
			System.out.println("On doit attendre " + Temps.toString(tEth) + " pour collecter " + cout.ether() + " d'éther");
		}
		
		return Math.max(tMat, tEth);
	}

	@Override
	protected EtatIle newEtatFils(EtatIle etatInitial) throws Exception {
		return new EtatIle(etatInitial, this);
	}

	public void addVaisseau(TypeVaisseau type, Ile ileMere) {
		_vaisseaux.add(new Vaisseau(type, ileMere));
	}
	
	public Joueur getJoueur()
	{
		return _entite;
	}
}
